/*-------------------------------------------------------------------------
 *
 * ip.c
 *	  支持 IPv6 的网络访问。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/common/ip.c
 *
 * 本文件及 IPV6 实现最初由 Nigel Kukard <nkukard@lbsd.net> 提供，Linux Based Systems Design
 * http://www.lbsd.net.
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#include <sys/file.h>

#include "common/ip.h"



#ifdef	HAVE_UNIX_SOCKETS
static int	fc_getaddrinfo_unix(const char *fc_path,
							 const struct addrinfo *fc_hintsp,
							 struct addrinfo **fc_result);

static int	fc_getnameinfo_unix(const struct sockaddr_un *fc_sa, int fc_salen,
							 char *fc_node, int fc_nodelen,
							 char *fc_service, int fc_servicelen,
							 int fc_flags);
#endif


/*
 *	pg_getaddrinfo_all - 获取 Unix、IPv4 和 IPv6 套接字的地址信息
 */
int pg_getaddrinfo_all(const char *fc_hostname, const char *fc_servname,
				   const struct addrinfo *fc_hintp, struct addrinfo **fc_result)
{
	int			fc_rc;

	/* 不是所有版本的 getaddrinfo() 在失败时都会将 *result 置为零 */
	*fc_result = NULL;

#ifdef HAVE_UNIX_SOCKETS
	if (fc_hintp->ai_family == AF_UNIX)
		return fc_getaddrinfo_unix(fc_servname, fc_hintp, fc_result);
#endif

	/* NULL 对 getaddrinfo() 有特殊意义。 */
	fc_rc = getaddrinfo((!fc_hostname || fc_hostname[0] == '\0') ? NULL : fc_hostname,
					 fc_servname, fc_hintp, fc_result);

	return fc_rc;
}


/*
 *	pg_freeaddrinfo_all - 释放 IPv4、IPv6 或 Unix 的 addrinfo 结构
 *
 * 注意：原始提示结构的 ai_family 字段必须被传递
 * 这样我们才能判断 addrinfo 结构是由系统的
 * getaddrinfo() 例程构建的还是由我们自己的 getaddrinfo_unix() 例程构建的。一些版本
 * 的 getaddrinfo() 可能会愿意返回 AF_UNIX 地址，因此
 * 不应该仅查看 addrinfo 本身的 ai_family。
 */
void pg_freeaddrinfo_all(int fc_hint_ai_family, struct addrinfo *fc_ai)
{
#ifdef HAVE_UNIX_SOCKETS
	if (fc_hint_ai_family == AF_UNIX)
	{
		/* 结构由 getaddrinfo_unix 构建（见 pg_getaddrinfo_all） */
		while (fc_ai != NULL)
		{
			struct addrinfo *fc_p = fc_ai;

			fc_ai = fc_ai->ai_next;
			free(fc_p->ai_addr);
			free(fc_p);
		}
	}
	else
#endif							/* HAVE_UNIX_SOCKETS */
	{
		/* 结构由 getaddrinfo() 构建 */
		if (fc_ai != NULL)
			freeaddrinfo(fc_ai);
	}
}


/*
 *	pg_getnameinfo_all - 获取 Unix、IPv4 和 IPv6 套接字的名称信息
 *
 * 此例程的 API 与标准的 getnameinfo() 定义有两个不同之处：
 * 首先，addr 参数被声明为 sockaddr_storage 而不是 struct sockaddr，其次，node 和 service 字段
 * 保证即使在失败返回时也会填充一些内容。
 */
int pg_getnameinfo_all(const struct sockaddr_storage *fc_addr, int fc_salen,
				   char *fc_node, int fc_nodelen,
				   char *fc_service, int fc_servicelen,
				   int fc_flags)
{
	int			fc_rc;

#ifdef HAVE_UNIX_SOCKETS
	if (fc_addr && fc_addr->ss_family == AF_UNIX)
		fc_rc = fc_getnameinfo_unix((const struct sockaddr_un *) fc_addr, fc_salen,
							  fc_node, fc_nodelen,
							  fc_service, fc_servicelen,
							  fc_flags);
	else
#endif
		fc_rc = getnameinfo((const struct sockaddr *) fc_addr, fc_salen,
						 fc_node, fc_nodelen,
						 fc_service, fc_servicelen,
						 fc_flags);

	if (fc_rc != 0)
	{
		if (fc_node)
			strlcpy(fc_node, "???", fc_nodelen);
		if (fc_service)
			strlcpy(fc_service, "???", fc_servicelen);
	}

	return fc_rc;
}


#if defined(HAVE_UNIX_SOCKETS)

/* -------
 *	getaddrinfo_unix - 使用兼容 IPv6 的 API 获取 Unix 套接字信息
 *
 *	错误：即使 hintsp 为 NULL 或
 *		  ai_socktype 为 0 也仅设置一个 addrinfo
 *		  不支持 AI_CANONNAME。
 * -------
 */
static int fc_getaddrinfo_unix(const char *fc_path, const struct addrinfo *fc_hintsp,
				 struct addrinfo **fc_result)
{
	struct addrinfo fc_hints;
	struct addrinfo *fc_aip;
	struct sockaddr_un *fc_unp;

	*fc_result = NULL;

	MemSet(&fc_hints, 0, sizeof(fc_hints));

	if (strlen(fc_path) >= sizeof(fc_unp->sun_path))
		return EAI_FAIL;

	if (fc_hintsp == NULL)
	{
		fc_hints.ai_family = AF_UNIX;
		fc_hints.ai_socktype = SOCK_STREAM;
	}
	else
		memcpy(&fc_hints, fc_hintsp, sizeof(fc_hints));

	if (fc_hints.ai_socktype == 0)
		fc_hints.ai_socktype = SOCK_STREAM;

	if (fc_hints.ai_family != AF_UNIX)
	{
		/* 不应调用此函数 */
		return EAI_FAIL;
	}

	fc_aip = calloc(1, sizeof(struct addrinfo));
	if (fc_aip == NULL)
		return EAI_MEMORY;

	fc_unp = calloc(1, sizeof(struct sockaddr_un));
	if (fc_unp == NULL)
	{
		free(fc_aip);
		return EAI_MEMORY;
	}

	fc_aip->ai_family = AF_UNIX;
	fc_aip->ai_socktype = fc_hints.ai_socktype;
	fc_aip->ai_protocol = fc_hints.ai_protocol;
	fc_aip->ai_next = NULL;
	fc_aip->ai_canonname = NULL;
	*fc_result = fc_aip;

	fc_unp->sun_family = AF_UNIX;
	fc_aip->ai_addr = (struct sockaddr *) fc_unp;
	fc_aip->ai_addrlen = sizeof(struct sockaddr_un);

	strcpy(fc_unp->sun_path, fc_path);

	/*
	 * 如果提供的路径以 @ 开头，用零字节替换它以供
	 * 内部表示。在这种模式下，整个 sun_path 是地址，
	 * 包括尾随零字节。但我们将地址长度设置为
	 * 仅包含原始字符串的长度。这样，尾随的零字节将不会出现在
	 * 操作系统的任何网络或套接字列表中。这只是一种约定，其他
	 * 包也遵循这一点。
	 */
	if (fc_path[0] == '@')
	{
		fc_unp->sun_path[0] = '\0';
		fc_aip->ai_addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(fc_path);
	}

	/*
	 * 填充 sun_len 的标准建议是将其设置为
	 * 结构大小（与实际路径长度无关）。然而，这在 AIX 7.1 上
	 * 会产生整数溢出警告，因为 sun_len 只是
	 * uint8，但结构大小超过 255 字节。很可能在该平台上没有人
	 * 在关注 sun_len，但我们必须对其做点什么。
	 * 为了抑制警告，将结构大小限制为
	 * 在 sun_len 中能容纳的大小。
	 */
#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
	fc_unp->sun_len = Min(sizeof(struct sockaddr_un),
					   ((size_t) 1 << (sizeof(fc_unp->sun_len) * BITS_PER_BYTE)) - 1);
#endif

	return 0;
}

/*
 * 将地址转换为主机名。
 */
static int fc_getnameinfo_unix(const struct sockaddr_un *fc_sa, int fc_salen,
				 char *fc_node, int fc_nodelen,
				 char *fc_service, int fc_servicelen,
				 int fc_flags)
{
	int			fc_ret;

	/* 无效的参数。 */
	if (fc_sa == NULL || fc_sa->sun_family != AF_UNIX ||
		(fc_node == NULL && fc_service == NULL))
		return EAI_FAIL;

	if (fc_node)
	{
		fc_ret = snprintf(fc_node, fc_nodelen, "%s", "[local]");
		if (fc_ret < 0 || fc_ret >= fc_nodelen)
			return EAI_MEMORY;
	}

	if (fc_service)
	{
		/*
		 * 检查它是否看起来像一个抽象套接字，但也可能
		 * 只是一个空字符串。
		 */
		if (fc_sa->sun_path[0] == '\0' && fc_sa->sun_path[1] != '\0')
			fc_ret = snprintf(fc_service, fc_servicelen, "@%s", fc_sa->sun_path + 1);
		else
			fc_ret = snprintf(fc_service, fc_servicelen, "%s", fc_sa->sun_path);
		if (fc_ret < 0 || fc_ret >= fc_servicelen)
			return EAI_MEMORY;
	}

	return 0;
}
#endif							/* HAVE_UNIX_SOCKETS */
